/*****************************************************************************
 *   Copyright 2005 Tim A Wang
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 ****************************************************************************/
package com.jb.web.bean.custom;

import com.jb.framework.ModelBean;
import com.jb.framework.PersistentManagerRegistry;
import com.jb.util.ClassUtil;
import com.jb.web.context.WebContext;
import com.jb.web.model.DBDescriptor;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;

import java.nio.channels.FileChannel;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;


/**
 * Java Bean wrapper that implements a TreeNode, for java.io.File
 */
public class FileNode extends ModelBean implements TreeNode {
    static final long serialVersionUID = 123123L;
    static final String SMALL = "small";
    static final String ICONS = "icons";

    static {
        PersistentManagerRegistry.registerDescriptor(FileNode.class,
            new DBDescriptor() {
                public int getFieldLength(String fieldName) {
                    if ("name".equals(fieldName)) {
                        return 64;
                    } else if ("title".equals(fieldName)) {
                        return 64;
                    } else if ("fileLocation".equals(fieldName)) {
                        return 1024;
                    } else {
                        return super.getFieldLength(fieldName);
                    }
                }
            });
    }

    protected transient File mFile;
    FileListModel model = null;
    transient FileNode mParent;
    List cachedList;

    /**
     * Populate files of this node from database.
     */
    List fileList;
    List mChildren = null;
    String author;
    String authorId;
    String cachedExt;
    String category;
    String ext;
    String fileLocation;
    String link;
    String mFileName;
    String size;
    String title;
    boolean showFiles = false;
    int access;

    public static TreeModel getFileTreeModel(String top, String location, boolean withFile, String ext) {
        FileNode fn = createFileNode(FileNode.class.getName());
        fn.init(top, location, withFile, ext);

        return new DefaultTreeModel(fn);
    }

    public static FileNode createFileNode(String top, String fileName, boolean showFiles, String ext) {
        FileNode fn = createFileNode(FileNode.class.getName());
        fn.init(top, fileName, showFiles, ext);

        return fn;
    }

    public static FileNode createFileNode(String className, String top, String fileName, boolean showFiles, String ext) {
        FileNode fn = createFileNode(className);
        fn.init(top, fileName, showFiles, ext);

        return fn;
    }

    public void setAccess(int access) {
        this.access = access;
    }

    public int getAccess() {
        return access;
    }

    public String getAccessString() {
        if (access == PRIVATE) {
            return "Private";
        } else if (access == FRIEND) {
            return "Friends";
        } else {
            return "Public";
        }
    }

    public boolean getAllowsChildren() {
        return mFile.isDirectory();
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthorId(String authorId) {
        this.authorId = authorId;
    }

    public String getAuthorId() {
        return authorId;
    }

    public void setCategory(String category) {
        this.category = category;
    }

    public String getCategory() {
        return category;
    }

    public TreeNode getChildAt(int i) {
        return (getChildrenInternal() == null) ? null : (TreeNode) getChildrenInternal().get(i);
    }

    public int getChildCount() {
        return getChildrenInternal().size();
    }

    public List getChildren() {
        return getChildrenInternal();
    }

    /**
     * Returns date the file modified
     *
     * @return date modified.
     */
    public Date getDate() {
        return (mFile == null) ? null : new Date(mFile.lastModified());
    }

    public File getFile() {
        return mFile;
    }

    public void setFileLocation(String fileLocation) {
        this.fileLocation = fileLocation;
    }

    public String getFileLocation() {
        return fileLocation;
    }

    /**
     * Populates children from filesytem with ext
     *
     * @param ctx contextual container
     * @param ext for filtering
     *
     * @return List of children
     */
    public List getFiles(WebContext ctx, String ext) {
        if (ext == null) {
            ctx.addErrorMessage(this, "File extention is expected for a file list");

            return Collections.EMPTY_LIST;
        }

        if ((cachedList != null) && ext.equals(cachedExt)) {
            return cachedList;
        }

        if (this.model != null) {
            fileList = model.getList(ctx);
        } else {
            fileList = new ArrayList();

            File[] fileArray = mFile.listFiles(new FileExtFilter(ext));

            if (fileArray != null) {
                for (int i = 0; i < fileArray.length; i++) {
                    if (fileArray[i].isFile()) {
                        FileNode file = createFileNode(this.getClass().getName());
                        file.init(fileArray[i], true);

                        file.setFileLocation(getFileUri(mFile.getName()));
                        fileList.add(file);
                    }
                }
            }
        }

        cachedList = fileList;
        cachedExt = ext;

        return fileList;
    }

    public String getFullPath() {
        return getFileLocation() + "/" + getName();
    }

    public String getIconPath() {
        String src = getFileLocation() + File.separator + ICONS + File.separator + getName();

        if (!src.toLowerCase().endsWith("jpg") && !src.toLowerCase().endsWith("gif")) {
            src = "resources/images/ext/txt.gif";

            //handle none picture file
            if (src.endsWith("mid")) {
                src = "resources/images/ext/mid.gif";
            }

            if (src.endsWith("mpg")) {
                src = "resources/images/ext/mpg.gif";
            }

            if (src.endsWith("mpeg")) {
                src = "resources/images/ext/mpeg.gif";
            }

            if (src.endsWith("txt")) {
                src = "resources/images/ext/txt.gif";
            }

            if (src.endsWith("xml")) {
                src = "resources/images/ext/xml.gif";
            }

            if (src.endsWith("swf")) {
                src = "resources/images/ext/swf.gif";
            }
        }
        return src;
    }

    public int getIndex(TreeNode file) {
        return getChildrenInternal().indexOf(file);
    }

    public boolean isLeaf() {
        return mFile==null || !mFile.isDirectory();
    }

    public void setLink(String link) {
        this.link = link;
    }

    public String getLink() {
        return link;
    }

    public void setModel(FileListModel model) {
        this.model = model;
        invalidateCache();
    }

    //this will change underline file name if allowed by platform
    public void setName(String name) {
        mFileName = name;

        if ((name == null) || (mFile == null) || name.equals(mFile.getName())) {
            return;
        }

        File newFile = new File(getFileUri(name));

        if (mFile.renameTo(newFile)) {
            mFile = newFile;
        }
    }

    public String getName() {
        return (mFile == null) ? mFileName : mFile.getName();
    }

    public String getOriginalPath(WebContext ctx) {
        return ctx.getSession().getPersonalContentTop();
    }

    /**
     * Sets parent node.
     *
     * @param fileNode new parent node.
     */
    public void setParent(FileNode fileNode) {
        mParent = fileNode;
    }

    /**
     * Returns parent node.
     *
     * @return parent node.
     */
    public TreeNode getParent() {
        return mParent;
    }

    public String getPath() {
        if (this.getParent() instanceof FileNode) {
            return ((FileNode) getParent()).getPath() + "/" + getFileLocation();
        }

        return getFileLocation();
    }

    /**
     * Returns file size.
     *
     * @return size of the file
     */
    public long getSize() {
        return mFile.length();
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getTitle() {
        return title;
    }

    /**
     * Adds a child node.
     *
     * @param fileNode child node to be added.
     */
    public void addChild(FileNode fileNode) {
        fileNode.setParent(this);
        getChildrenInternal().add(fileNode);
    }

    public Enumeration children() {
        return getChildrenInternal().elements();
    }

    public void createSubDir(String newDirName) {
        if (mFile.isDirectory()) {
            File newFile = new File(mFile.getPath() + File.separator + newDirName);

            if (!newFile.exists()) {
                newFile.mkdirs();
            }
        }

        this.cachedList = null;
    }

    /**
     * Deletes this node permernately.
     *
     * @param ctx runtime context.
     */
    public void delete(WebContext ctx) {
        if (mChildren != null) {
            Iterator iter = mChildren.iterator();

            while (iter.hasNext()) {
                FileNode child = (FileNode) iter.next();
                child.delete(ctx);
            }
        }

        if (fileList != null) {
            Iterator iter = fileList.iterator();

            while (iter.hasNext()) {
                FileNode child = (FileNode) iter.next();
                child.delete(ctx);
            }
        }

        if (mFileName != null) { //delete the directory
            delete();
        } else {
            String path = ctx.getWebTop() + getIconPath();
            File file = new File(path);

            if (file.exists()) {
                file.delete();
            }

            //orignal
            path = ctx.getWebTop() + getFullPath();
            file = new File(path);

            if (file.isDirectory()) {
                this.deleteDirectory(file);
            } else if (file.exists()) {
                file.delete();
            }
        }

        invalidateCache();

        try {
            super.delete(ctx);
        } catch (Exception e) {
            //
        }
    }

    public boolean delete() {
        if (mFile == null) {
            return false;
        }

        this.cachedList = null;

        if (mFile.isDirectory()) {
            return deleteDirectory(mFile);
        } else {
            return mFile.delete();
        }
    }

    /**
     * Invalidates all transite data, including cached children list.
     */
    public void invalidate() {
        if (cachedList != null) {
            for (Iterator iter = cachedList.iterator(); iter.hasNext();) {
                FileNode fileNode = (FileNode) iter.next();
                fileNode.invalidate();
            }
        }

        cachedList = null;
    }

    //this is called a file is received
    public void process(WebContext ctx, String topLocation) {
        String genFileName = getLink();
        File file = new File(genFileName);

        if (file.isDirectory()) {
            return;
        }

        saveFile(ctx, topLocation);
    }

    public String toString() {
        return (this.getTitle() == null) ? getName() : getTitle();
    }

    protected boolean copyFile(String from, String to) {
        FileChannel srcChannel = null;

        // Create channel on the destination
        FileChannel dstChannel = null;

        try {
            // Create channel on the source
            srcChannel = new FileInputStream(from).getChannel();

            // Create channel on the destination
            dstChannel = new FileOutputStream(to).getChannel();

            // Copy file contents from source to destination
            dstChannel.transferFrom(srcChannel, 0, srcChannel.size());

            // Close the channels
            srcChannel.close();
            srcChannel = null;
            dstChannel.close();
            dstChannel = null;

            return true;
        } catch (IOException e) {
            return false;
        } finally {
            try {
                if (srcChannel != null) {
                    srcChannel.close();
                }
            } catch (Exception e) {
            }

            try {
                if (dstChannel != null) {
                    dstChannel.close();
                }
            } catch (Exception e) {
            }
        }
    }

    //delete directory and sub directories recursively 
    protected boolean deleteDirectory(File path) {
        if (path.exists()) {
            File[] files = path.listFiles();

            for (int i = 0; i < files.length; i++) {
                if (files[i].isDirectory()) {
                    deleteDirectory(files[i]);
                } else {
                    files[i].delete();
                }
            }
        }

        return (path.delete());
    }

    protected void init(String top, String fileName, boolean showFiles, String ext) {
        if (fileName.lastIndexOf("/") != -1) {
            setFileLocation(fileName.substring(0, fileName.lastIndexOf("/")));
        } else {
            setFileLocation("");
        }

        if (!top.endsWith(File.separator)) {
            top = top + File.separator;
        }

        mFile = new File(top + fileName);
        mFileName = fileName.substring(fileName.lastIndexOf("/") + 1);
        setName(mFileName);

        if (!mFile.exists()) {
            mFile.mkdirs();
        }

        this.ext = ext;
        this.showFiles = showFiles;
    }

    //private constructor
    protected void init(File pFile, boolean showFiles) {
        mFile = pFile;
        this.showFiles = showFiles;
        this.setName(pFile.getName());
        this.setId(pFile.getName());
    }

    protected boolean moveFile(String from, String to) {
        if (copyFile(from, to)) {
            return new File(from).delete();
        } else {
            return false;
        }
    }

    protected void saveFile(WebContext ctx, String topLocation) {
        String genFileName = getLink();
        File file = new File(genFileName);

        if (file.isDirectory()) {
            return;
        }

        try {
            if (moveFile(genFileName, topLocation + File.separator + getName())) {
                setLink(getFileLocation() + File.separator + getName());
            }
        } catch (Exception e) {
            ctx.addErrorMessage(this, e.getLocalizedMessage());
            delete(ctx);
        }
    }

    static FileNode createFileNode(String className) {
        return (FileNode) ClassUtil.createInstance(className);
    }

    private Vector getChildrenInternal() {
        if (mChildren == null) {
            populate(showFiles);
        }

        return (Vector) mChildren;
    }

    private String getFileUri(String name) {
        if ((getFileLocation() == null) || (getFileLocation().length() == 0)) {
            return name;
        } else {
            return getFileLocation() + "/" + name;
        }
    }

    private void invalidateCache() {
        cachedList = null;
    }

    private List populate(String ext, boolean showFile) {
        List list = new ArrayList();

        File[] fileArray = mFile.listFiles(new FileExtFilter(ext));

        if (fileArray != null) {
            for (int i = 0; i < fileArray.length; i++) {
                if (fileArray[i].isFile() && showFile) {
                    FileNode file = createFileNode(this.getClass().getName());
                    file.init(fileArray[i], true);

                    file.setFileLocation(getFileUri(mFile.getName()));
                    list.add(file);
                }
            }
        }

        return list;
    }

    private void populate(boolean withFile) {
        mChildren = new Vector();

        File[] files = mFile.listFiles();

        if (files == null) {
            return;
        }

        FileExtFilter fef = new FileExtFilter(ext);

        //add files and directory as children
        for (int i = 0; i < files.length; i++) {
            if ((withFile && fef.accept(files[i], files[i].getName())) ||
                    (files[i].isDirectory() && !ICONS.equals(files[i].getName()) && !SMALL.equals(files[i].getName()))) {
                FileNode fileNode = createFileNode(this.getClass().getName());
                fileNode.init(files[i], withFile);
                fileNode.setFileLocation(getFileUri(mFile.getName()));
                mChildren.add(fileNode);
            }
        }
    }
}


class FileExtFilter implements FilenameFilter {
    String ext;

    public FileExtFilter(String ext) {
        this.ext = ext;
    }

    public boolean accept(File path, String name) {
        if ((ext == null) || (ext.length() == 0) || "*".equals(ext)) {
            return true;
        }

        String fileExt = name.substring(name.lastIndexOf(".") + 1);

        return ext.toUpperCase().indexOf(fileExt.toUpperCase()) != -1;
    }
}
